using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace GoogleNet
{
    /// <summary>
    /// Class that contains simplified methods for building a KML document with the necessary geometric 
    /// elements.
    /// </summary>
    public class KmlHelper
    {
        private KmlDocument mainDocument;

        /// <summary>
        /// Initializes a new instance of the <see cref="KmlHelper"/> class.
        /// </summary>
        /// <param name="kmlDocumentName">Name of the KML document.</param>
        public KmlHelper(string kmlDocumentName)
        {
            mainDocument = new KmlDocument();
            mainDocument.ID = kmlDocumentName;
        }

        #region AddPoint
        /// <summary>
        /// Adds the specified point to the KML document.
        /// </summary>
        /// <param name="name">The name of this point placemark, to be shown as the label for this point.</param>
        /// <param name="latitude">The latitude of the point placemark.</param>
        /// <param name="longitude">The longitude of the point placemark.</param>
        /// <param name="altitude">The altitude of the point placemark.</param>
        public void AddPoint(string name, double latitude, double longitude, double altitude)
        {
            AddPoint(name, latitude, longitude, altitude, KmlAltitudeModes.absolute, false, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the specified point to the KML document.
        /// </summary>
        /// <param name="name">The name of this point placemark, to be shown as the label for this point.</param>
        /// <param name="latitude">The latitude of the point placemark.</param>
        /// <param name="longitude">The longitude of the point placemark.</param>
        /// <param name="altitude">The altitude of the point placemark.</param>
        /// <param name="pointStyle">The point style.</param>
        /// <param name="pointStyle">The style used to represent the point placemark.</param>
        public void AddPoint(string name, double latitude, double longitude, double altitude, KmlIconStyle pointStyle)
        {
            AddPoint(name, latitude, longitude, altitude, KmlAltitudeModes.absolute, false, string.Empty, string.Empty, pointStyle);
        }

        /// <summary>
        /// Adds the specified point to the KML document.
        /// </summary>
        /// <param name="name">The name of this point placemark, to be shown as the label for this point.</param>
        /// <param name="latitude">The latitude of the point placemark.</param>
        /// <param name="longitude">The longitude of the point placemark.</param>
        /// <param name="altitude">The altitude of the point placemark.</param>
        /// <param name="description">A description for the point placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for this point placemark (NOTE: not processed at all by GoogleEarth).</param>
        public void AddPoint(string name, double latitude, double longitude, double altitude, 
                            string description, string comments)
        {
            AddPoint(name, latitude, longitude, altitude, KmlAltitudeModes.absolute, false, description, comments);
        }

        /// <summary>
        /// Adds the specified point to the KML document.
        /// </summary>
        /// <param name="name">The name of this point placemark, to be shown as the label for this point.</param>
        /// <param name="latitude">The latitude of the point placemark.</param>
        /// <param name="longitude">The longitude of the point placemark.</param>
        /// <param name="altitude">The altitude of the point placemark.</param>
        /// <param name="description">A description for the point placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for this point placemark (NOTE: not processed at all by GoogleEarth).</param>
        /// <param name="pointStyle">The style used to represent the point placemark.</param>
        public void AddPoint(string name, double latitude, double longitude, double altitude,
                            string description, string comments, KmlIconStyle pointStyle)
        {
            AddPoint(name, latitude, longitude, altitude, KmlAltitudeModes.absolute, false, description, comments, pointStyle);
        }

        /// <summary>
        /// Adds the specified point to the KML document.
        /// </summary>
        /// <param name="name">The name of this point placemark, to be shown as the label for this point.</param>
        /// <param name="latitude">The latitude of the point placemark.</param>
        /// <param name="longitude">The longitude of the point placemark.</param>
        /// <param name="altitude">The altitude of the point placemark.</param>
        /// <param name="altitudeMode">Specifies how altitude components in the coordinates element are interpreted.</param>
        /// <param name="extrude">Specifies whether to connect the geometric primitive (icon, line, polygon) to the ground. 
        /// Extrusion requires that the geometry's altitudeMode be either relativeToGround or 
        /// absolute and that within the coordinates element, the altitude component be greater than 0 (that is, in the air).</param>
        /// <param name="description">A description for the point placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for this point placemark (NOTE: not processed at all by GoogleEarth).</param>
        public void AddPoint(string name, double latitude, double longitude, double altitude, 
                            KmlAltitudeModes altitudeMode, bool extrude, string description, string comments)
        {
            KmlIconStyle newPointStyle = new KmlIconStyle();
            newPointStyle.ColorMode = KmlColorModes.normal;
            newPointStyle.Color = Color.Red;          

            AddPoint(name, latitude, longitude, altitude, altitudeMode, extrude, description, comments, newPointStyle);
        }

        /// <summary>
        /// Adds the specified point to the KML document.
        /// </summary>
        /// <param name="name">The name of this point placemark, to be shown as the label for this point.</param>
        /// <param name="latitude">The latitude of the point placemark.</param>
        /// <param name="longitude">The longitude of the point placemark.</param>
        /// <param name="altitude">The altitude of the point placemark.</param>
        /// <param name="altitudeMode">Specifies how altitude components in the coordinates element are interpreted.</param>
        /// <param name="extrude">Specifies whether to connect the geometric primitive (icon, line, polygon) to the ground.
        /// Extrusion requires that the geometry's altitudeMode be either relativeToGround or
        /// absolute and that within the coordinates element, the altitude component be greater than 0 (that is, in the air).</param>
        /// <param name="description">A description for the point placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for this point placemark (NOTE: not processed at all by GoogleEarth).</param>
        /// <param name="pointStyle">The style used to represent the point placemark.</param>
        public void AddPoint(string name, double latitude, double longitude, double altitude,
                            KmlAltitudeModes altitudeMode, bool extrude, string description, string comments, KmlIconStyle pointStyle)
        {
            KmlIconStyle newPointStyle = new KmlIconStyle();
            newPointStyle.ColorMode = KmlColorModes.normal;
            newPointStyle.Color = Color.Red;

            KmlStyle newPlacemarkStyle = new KmlStyle();
            newPlacemarkStyle.ID = mainDocument.ID + "PointStyle_" + mainDocument.Features.Count.ToString();
            newPlacemarkStyle.IconStyle = pointStyle;

            KmlPoint newPoint = new KmlPoint(latitude, longitude, altitude);
            newPoint.AltitudeMode = altitudeMode;
            newPoint.ID = mainDocument.ID + "Point_" + mainDocument.Features.Count.ToString();
            newPoint.Extrude = extrude;

            KmlPlacemark newPlacemark = new KmlPlacemark();
            newPlacemark.ID = mainDocument.ID + "Placemark_" + mainDocument.Features.Count.ToString();
            newPlacemark.Name = name;
            newPlacemark.Geometry = newPoint;
            newPlacemark.StyleSelector = newPlacemarkStyle;
            newPlacemark.StyleUrl = "#" + newPlacemarkStyle.ID;
            if (description != string.Empty)
                newPlacemark.Description = description;
            if (comments != string.Empty)
                newPlacemark.Metadata = comments;

            mainDocument.Features.Add(newPlacemark);
        } 
        #endregion

        #region AddLine
        /// <summary>
        /// Adds the line specified by the list of point coordinates to the KML document.
        /// </summary>
        /// <param name="name">The name of this line placemark, to be shown as the label for this line.</param>
        /// <param name="points">The list of point coordinates that compose the line.</param>
        public void AddLine(string name, List<KmlPointCoordinates> points)
        {
            AddLine(name, points, KmlAltitudeModes.absolute, Color.Red, 1, false, false, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the line specified by the list of point coordinates to the KML document.
        /// </summary>
        /// <param name="name">The name of this line placemark, to be shown as the label for this line.</param>
        /// <param name="points">The list of point coordinates that compose the line.</param>
        /// <param name="lineColor">Color of the line.</param>
        /// <param name="lineWidth">Width of the line.</param>
        public void AddLine(string name, List<KmlPointCoordinates> points, Color lineColor, int lineWidth)
        {
            AddLine(name, points, KmlAltitudeModes.absolute, lineColor, lineWidth, false, false, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the line specified by the list of point coordinates to the KML document.
        /// </summary>
        /// <param name="name">The name of this line placemark, to be shown as the label for this line.</param>
        /// <param name="points">The list of point coordinates that compose the line.</param>
        /// <param name="altitudeMode">Specifies how altitude components in the coordinates element are interpreted.</param>
        /// <param name="lineColor">Color of the line.</param>
        /// <param name="lineWidth">Width of the line.</param>
        /// <param name="extrude">Specifies whether to connect the geometric primitive (icon, line, polygon) to the ground.
        /// Extrusion requires that the geometry's altitudeMode be either relativeToGround or
        /// absolute and that within the coordinates element, the altitude component be greater than 0 (that is, in the air).</param>
        /// <param name="tesselate">Specifies whether to allow lines and paths to follow the terrain.
        /// This specification applies only to LineStrings (paths) and LinearRings
        /// (polygons) that have an altitudeMode of clampToGround.
        /// Very long lines should enable tessellation so that they follow the curvature of the earth
        /// (otherwise, they may go underground and be hidden).</param>
        /// <param name="description">A description for the line placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for the line placemark (NOTE: not processed at all by GoogleEarth).</param>
        public void AddLine(string name, List<KmlPointCoordinates> points, KmlAltitudeModes altitudeMode,
                            Color lineColor, int lineWidth, bool extrude, bool tesselate, string description, string comments)
        {
            KmlLineStyle newLineStyle = new KmlLineStyle();
            newLineStyle.ColorMode = KmlColorModes.normal;
            newLineStyle.Color = lineColor;
            newLineStyle.Width = lineWidth;

            KmlStyle newPlacemarkStyle = new KmlStyle();
            newPlacemarkStyle.ID = mainDocument.ID + "LineStyle_" + mainDocument.Features.Count.ToString();
            newPlacemarkStyle.LineStyle = newLineStyle;

            KmlLineString newLine = new KmlLineString();
            newLine.ID = mainDocument.ID + "LineString_" + mainDocument.Features.Count.ToString();
            newLine.AltitudeMode = altitudeMode;
            newLine.Extrude = extrude;
            newLine.Tesselate = tesselate;
            newLine.Coordinates = points;

            KmlPlacemark newPlacemark = new KmlPlacemark();
            newPlacemark.ID = mainDocument.ID + "Placemark_" + mainDocument.Features.Count.ToString();
            newPlacemark.Name = name;
            newPlacemark.Geometry = newLine;
            newPlacemark.StyleSelector = newPlacemarkStyle;
            newPlacemark.StyleUrl = "#" + newPlacemarkStyle.ID;
            if (description != string.Empty)
                newPlacemark.Description = description;
            if (comments != string.Empty)
                newPlacemark.Metadata = comments;

            mainDocument.Features.Add(newPlacemark);
        } 
        #endregion

        #region AddPolygon

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing)
        {
            AddPolygon(name, outerRing, null, KmlAltitudeModes.absolute, false, false, true, Color.Red, true, 
                Color.DarkRed, 1, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="description">A description for the polygon placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for the polygon placemark (NOTE: not processed at all by GoogleEarth).</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing, string description, string comments)
        {
            AddPolygon(name, outerRing, null, KmlAltitudeModes.absolute, false, false, true, Color.Red, true,
                Color.DarkRed, 1, description, comments);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="innerRings">The inner rings.</param>
        /// <param name="isFilledWithColor">If set to <c>true</c> the polygon is filled with color.</param>
        /// <param name="fillColor">Color used to fill the polygon.</param>
        /// <param name="isOutlined">If set to <c>true</c> the polygon is drawn with an outline.</param>
        /// <param name="outlineColor">Color of the outline of the polygon.</param>
        /// <param name="outlineWidth">Width of the outline of the polygon.</param>
         public void AddPolygon(string name, List<KmlPointCoordinates> outerRing,
            bool isFilledWithColor, Color fillColor, bool isOutlined, Color outlineColor, int outlineWidth)
         {
            AddPolygon(name, outerRing, null, KmlAltitudeModes.absolute, false, false, isFilledWithColor, fillColor, isOutlined,
                outlineColor, outlineWidth, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="innerRings">The inner rings.</param>
        /// <param name="isFilledWithColor">If set to <c>true</c> the polygon is filled with color.</param>
        /// <param name="fillColor">Color used to fill the polygon.</param>
        /// <param name="isOutlined">If set to <c>true</c> the polygon is drawn with an outline.</param>
        /// <param name="outlineColor">Color of the outline of the polygon.</param>
        /// <param name="outlineWidth">Width of the outline of the polygon.</param>
        /// <param name="description">A description for the polygon placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for the polygon placemark (NOTE: not processed at all by GoogleEarth).</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing, 
           bool isFilledWithColor, Color fillColor, bool isOutlined, Color outlineColor, int outlineWidth,
            string description, string comments)
        {
            AddPolygon(name, outerRing, null, KmlAltitudeModes.absolute, false, false, isFilledWithColor, fillColor, isOutlined,
                outlineColor, outlineWidth, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="innerRings">If the polygon is not "full", this is the list of point "rings" that compose
        /// the inner "holes" of the polygon.</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing, List<KmlLinearRing> innerRings)
        {
            AddPolygon(name, outerRing, innerRings, KmlAltitudeModes.absolute, false, false, true, Color.Red, true,
                Color.DarkRed, 1, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="innerRings">If the polygon is not "full", this is the list of point "rings" that compose
        /// the inner "holes" of the polygon.</param>
        /// <param name="isFilledWithColor">If set to <c>true</c> the polygon is filled with color.</param>
        /// <param name="fillColor">Color used to fill the polygon.</param>
        /// <param name="isOutlined">If set to <c>true</c> the polygon is drawn with an outline.</param>
        /// <param name="outlineColor">Color of the outline of the polygon.</param>
        /// <param name="outlineWidth">Width of the outline of the polygon.</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing, List<KmlLinearRing> innerRings,
            bool isFilledWithColor, Color fillColor, bool isOutlined, Color outlineColor, int outlineWidth)
        {
            AddPolygon(name, outerRing, innerRings, KmlAltitudeModes.absolute, false, false, isFilledWithColor, fillColor, isOutlined,
                outlineColor, outlineWidth, string.Empty, string.Empty);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="innerRings">If the polygon is not "full", this is the list of point "rings" that compose
        /// the inner "holes" of the polygon.</param>
        /// <param name="isFilledWithColor">If set to <c>true</c> the polygon is filled with color.</param>
        /// <param name="fillColor">Color used to fill the polygon.</param>
        /// <param name="isOutlined">If set to <c>true</c> the polygon is drawn with an outline.</param>
        /// <param name="outlineColor">Color of the outline of the polygon.</param>
        /// <param name="outlineWidth">Width of the outline of the polygon.</param>
        /// <param name="description">The description.</param>
        /// <param name="comments">The comments.</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing, List<KmlLinearRing> innerRings,
                                bool isFilledWithColor, Color fillColor, bool isOutlined, Color outlineColor, int outlineWidth, 
                                string description, string comments)
        {
            AddPolygon(name, outerRing, innerRings, KmlAltitudeModes.absolute, false, false, isFilledWithColor, fillColor, isOutlined,
                outlineColor, outlineWidth, description, comments);
        }

        /// <summary>
        /// Adds the specified polygon to the KML document.
        /// </summary>
        /// <param name="name">The name of this polygon placemark, to be shown as the label for this polygon.</param>
        /// <param name="outerRing">The list of point coordinates the composes the outer border of the polygon.</param>
        /// <param name="innerRings">If the polygon is not "full", this is the list of point "rings" that compose
        /// the inner "holes" of the polygon.</param>
        /// <param name="altitudeMode">Specifies how altitude components in the coordinates element are interpreted.</param>
        /// <param name="extrude">Specifies whether to connect the geometric primitive (icon, line, polygon) to the ground.
        /// Extrusion requires that the geometry's altitudeMode be either relativeToGround or
        /// absolute and that within the coordinates element, the altitude component be greater than 0 (that is, in the air).</param>
        /// <param name="tesselate">Specifies whether to allow lines and paths to follow the terrain.
        /// This specification applies only to LineStrings (paths) and LinearRings
        /// (polygons) that have an altitudeMode of clampToGround.
        /// Very long lines should enable tessellation so that they follow the curvature of the earth
        /// (otherwise, they may go underground and be hidden).</param>
        /// <param name="isFilledWithColor">If set to <c>true</c> the polygon is filled with color.</param>
        /// <param name="fillColor">Color used to fill the polygon.</param>
        /// <param name="isOutlined">If set to <c>true</c> the polygon is drawn with an outline.</param>
        /// <param name="outlineColor">Color of the outline of the polygon.</param>
        /// <param name="outlineWidth">Width of the outline of the polygon.</param>
        /// <param name="description">A description for the polygon placemark (can use limited HTML).</param>
        /// <param name="comments">Comments for the polygon placemark (NOTE: not processed at all by GoogleEarth).</param>
        public void AddPolygon(string name, List<KmlPointCoordinates> outerRing, List<KmlLinearRing> innerRings,
            KmlAltitudeModes altitudeMode, bool extrude, bool tesselate, bool isFilledWithColor, Color fillColor,
            bool isOutlined, Color outlineColor, int outlineWidth, string description, string comments)
        {
            KmlLineStyle outlineStyle = new KmlLineStyle();
            outlineStyle.ColorMode = KmlColorModes.normal;
            outlineStyle.Color = outlineColor;
            outlineStyle.Width = outlineWidth;

            KmlPolyStyle newPolyStyle = new KmlPolyStyle();
            newPolyStyle.Color = fillColor;
            newPolyStyle.Fill = isFilledWithColor;
            newPolyStyle.Outline = isOutlined;

            KmlStyle newPlacemarkStyle = new KmlStyle();
            newPlacemarkStyle.ID = mainDocument.ID + "PolygonStyle_" + mainDocument.Features.Count.ToString();
            newPlacemarkStyle.LineStyle = outlineStyle;
            newPlacemarkStyle.PolyStyle = newPolyStyle;

            KmlLinearRing outerBorder = new KmlLinearRing();
            outerBorder.Coordinates = outerRing;
            outerBorder.AltitudeMode = altitudeMode;
            outerBorder.Extrude = extrude;
            outerBorder.Tesselate = tesselate;
            outerBorder.ID = mainDocument.ID + "OuterRing_" + mainDocument.Features.Count.ToString();

            KmlPolygon newPolygon = new KmlPolygon();
            newPolygon.AltitudeMode = altitudeMode;
            newPolygon.Extrude = extrude;
            newPolygon.OuterBoundaryIs = outerBorder;
            newPolygon.Tesselate = tesselate;
            newPolygon.ID = mainDocument.ID + "Polygon_" + mainDocument.Features.Count.ToString();
            if (innerRings != null && innerRings.Count > 0)
            {
                newPolygon.InnerBoundaryIs = innerRings;
            }

            KmlPlacemark newPlacemark = new KmlPlacemark();
            newPlacemark.ID = mainDocument.ID + "Placemark_" + mainDocument.Features.Count.ToString();
            newPlacemark.Name = name;
            newPlacemark.Geometry = newPolygon;
            newPlacemark.StyleSelector = newPlacemarkStyle;
            newPlacemark.StyleUrl = "#" + newPlacemarkStyle.ID;
            if (description != string.Empty)
                newPlacemark.Description = description;
            if (comments != string.Empty)
                newPlacemark.Metadata = comments;

            mainDocument.Features.Add(newPlacemark);
        } 
        
        #endregion

        #region WriteToFile

        /// <summary>
        /// Writes to file the generated KML document.
        /// </summary>
        /// <param name="filename">The filename of the resulting KML generation.</param>
        public void WriteToFile(string filename)
        {
            KmlWriter.Write(mainDocument, filename, true);

        } 

        #endregion

        #region CalculateDistance

        /// <summary>
        /// Calculates the distance between two <see cref="KmlPointCoordinates"></see> as defined by their WGS-34 
        /// coordinates (latitude and longitude).
        /// </summary>
        /// <param name="position1">The first position.</param>
        /// <param name="position2">The second position.</param>
        /// <returns></returns>
        public static double CalculateDistance(KmlPointCoordinates position1,
                                               KmlPointCoordinates position2)
        {
            

            return CalculateDistance(position1.Latitude, position1.Longitude, 
                    position2.Latitude, position2.Longitude);
        }

        /// <summary>
        /// Calculates the distance between two points as defined by their WGS-34 coordinates (latitude and longitude).
        /// </summary>
        /// <param name="latitude1">The latitude1.</param>
        /// <param name="longitude1">The longitude1.</param>
        /// <param name="latitude2">The latitude2.</param>
        /// <param name="longitude2">The longitude2.</param>
        /// <returns></returns>
        private static double CalculateDistance(double latitude1, double longitude1, 
                                                double latitude2, double longitude2)
        {
            /*
                The Haversine formula according to Dr. Math.
                http://mathforum.org/library/drmath/view/51879.html
                
                dlon = lon2 - lon1
                dlat = lat2 - lat1
                a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
                c = 2 * atan2(sqrt(a), sqrt(1-a)) 
                d = R * c
                
                Where
                    * dlon is the change in longitude
                    * dlat is the change in latitude
                    * c is the great circle distance in Radians.
                    * R is the radius of a spherical Earth.
                    * The locations of the two points in 
                        spherical coordinates (longitude and 
                        latitude) are lon1,lat1 and lon2, lat2.
            */
            double dDistance = Double.MinValue;
            double dLat1InRad = latitude1 * (Math.PI / 180.0);
            double dLong1InRad = longitude1 * (Math.PI / 180.0);
            double dLat2InRad = latitude2 * (Math.PI / 180.0);
            double dLong2InRad = longitude2 * (Math.PI / 180.0);

            double dLongitude = dLong2InRad - dLong1InRad;
            double dLatitude = dLat2InRad - dLat1InRad;

            // Intermediate result a.
            double a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
                       Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) *
                       Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

            // Intermediate result c (great circle distance in Radians).
            double c = 2.0 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1.0 - a));

            // Distance.
            // const Double kEarthRadiusMiles = 3956.0;
            const Double kEarthRadiusKms = 6376.5;
            dDistance = kEarthRadiusKms * c;

            return dDistance;
        }

        #endregion  
    }
}
